from functools import partial
import inspect
import types

class FunctionCache(object):
    def __init__(self, func):
        self.cache = {} # {*args -> [**kwargs, value]}
        self.func = func

    def __call__(self, *args, **kwargs):
        try:
            cacheValue = self.cache[args]
            if cacheValue[0] == kwargs:
                return cacheValue[1]
            else:
                value = self.func(*args, **kwargs)
                self.cache.setdefault(args, [
                    kwargs,
                    value
                ])
                return value
        except KeyError:
            value = self.func(*args, **kwargs)
            self.cache.setdefault(args, [
                kwargs,
                value
            ])
            return value
        except TypeError:
            """
            Uncacheable. List or something not hashable passed as argument
            Better not to cache.
            """
            return self.func(*args, **kwargs)

    def __repr__(self):
        return self.func.__doc__

    def __get__(self, obj, objtype):
        return partial(self.__call__, obj)

def cacheable(func):
    return FunctionCache(func)

def getArgSpec(entity, passedTypes=set((types.MethodType, types.BuiltinMethodType, types.UnboundMethodType,
                                        types.FunctionType, types.BuiltinFunctionType))):
    tp = type(entity)
    if tp in passedTypes:
        pass
    elif tp == types.InstanceType or (not isinstance(entity, type) and isinstance(tp, type)):
        entity = entity.__call__
    elif tp == types.ClassType or isinstance(entity, type):
        entity = entity.__init__
    else:
        return []
    result = inspect.getargspec(entity)
    return result